import { AudioClip, AudioSource, Color, Label, Node, Prefab, Sprite, SpriteAtlas, SpriteFrame, UIOpacity, UITransform, director, find, instantiate, resources, tween, v3, view } from "cc";
import { EEvent, EGame, EGameMode, EntityType, EPath, EPlayerType, EPrefabName, EScene, ESkill, EText, ETips, EGameSystem, EMartialType, EMartialSystem, EGeneralSkill, EView, EEnemy, EColor, EPet, ESubView, EWeather } from "../Config/Enum";
import { PlayerData } from "../PlayerData";
import { Context } from "../Context";
import { GAME_SAVING_KEY } from "../Config/GameConfig";
import { BagManager } from "../Item/BagManager";
import { EEquipmentType, EItemQuality, EItemType, IItem } from "../Item/Item";
import ObjectPoolManager from "./ObjectPoolManager";
import { TipsManager } from "../TipsManager";
import { ResourceManager } from "./ResourceManager";
import { Util } from "../Util";
import { enemyRes, PicPath, PrefabPath, sceneRes } from "../Config/ResourceConfig";
import { EnemyManager } from "../EnemyManager";
import { EquipmentConfig } from "../Config/EquipmentConfig";
import { Equipment, IEquipment } from "../Item/Equipment";
import { GemConfig } from "../Config/GemConfig";
import { Gem, IGem } from "../Item/Gem";
import { GeneralData } from "../GeneralData";
import { bossList, enemyList } from "../Config/EnemyConfig";
import { StageConfig } from "../Config/StageConfig";
import { FOOD_RECOVER_RATE, JUNLING_RECOVER_AMOUNT, JUNLING_RECOVER_AMOUNT_DAILY, RESOURCE_RECOVER_INTERVAL, SHOP_REFRESH_INTERVAL, SystemUnlockConfig, SystemUnlockMap, WOODROCK_RECOVER_RATE } from "../Config/Config";
import { PetData } from "../PetData";

interface IEventHandler {
    cb: Function;
    ctx: unknown;
}

export class EventCenter {
    private map: Map<EEvent, Array<IEventHandler>> = new Map();

    on(event: EEvent, cb: Function, ctx: unknown) {
        if (this.map.has(event)) {
            this.map.get(event).push({ cb, ctx });
        } else {
            this.map.set(event, [{ cb, ctx }]);
        }
    }

    off(event: EEvent, cb: Function, ctx: unknown) {
        if (this.map.has(event)) {
            const index = this.map.get(event).findIndex((i) => cb === i.cb && i.ctx === ctx);
            index > -1 && this.map.get(event).splice(index, 1);
        }
    }

    emit(event: EEvent, ...params: unknown[]) {
        if (this.map.has(event)) {
            this.map.get(event).forEach(({ cb, ctx }) => {
                cb.apply(ctx, params);
            });
        }
    }

    clear() {
        this.map.clear();
    }
}

export class AudioManager {
    private static _instance: AudioManager;

    public static get Instance(): AudioManager {
        if (!this._instance) {
            this._instance = new AudioManager();
        }
        return this._instance;
    }

    private _audioCaches: Record<string, AudioClip> = {};
    private _audioSource: AudioSource;
    private _currentMusicPath: string = "";
    private _volume: number = 1.0;

    getCurrentMusic(): string {
        return this._currentMusicPath;
    }

    init(audioSouce: AudioSource) {
        this._audioSource = audioSouce;
        this._audioSource.loop = true;
        this._audioSource.volume = this._volume;
    }

    playSound(name: string, volume: number = 1.0) {
        if (!this._audioSource) return;
        let url = EPath.AUDIO_SOUND + name;
        if (this._audioCaches[url]) {
            this._audioSource?.playOneShot(this._audioCaches[url], volume);
        } else {
            resources.load(url, (err, audioClip) => {
                if (err) return;
                this._audioCaches[url] = audioClip as AudioClip;
                this._audioSource?.playOneShot(this._audioCaches[url], volume);
            });
        }
    }

    playMusic(name: string, volume: number = 1.0) {
        if (!this._audioSource) return;
        let url = EPath.AUDIO_MUSIC + name;
        this._audioSource.volume = this._volume;

        if (this._currentMusicPath === url && this._audioSource.playing) {
            return;
        }
        this._currentMusicPath = url;

        if (this._audioCaches[url]) {
            this._playClip(this._audioCaches[url]);
        } else {
            resources.load(url, (err, audioClip) => {
                if (err) return;
                this._audioCaches[url] = audioClip as AudioClip;
                if (this._currentMusicPath === url) {
                    this._playClip(audioClip as AudioClip);
                }
            });
        }
    }

    private _playClip(clip: AudioClip) {
        if (!this._audioSource) return;
        this._audioSource.stop();
        this._audioSource.clip = clip;
        this._audioSource.volume = this._volume;
        this._audioSource.play();
    }

    stopMusic() {
        if (!this._audioSource) return;
        this._audioSource.stop();
        this._currentMusicPath = "";
    }

    setMusicVolume(volume: number) {
        this._volume = Math.max(0, Math.min(1, volume));
        if (!this._audioSource) return;
        this._audioSource.volume = this._volume;
    }

    getMusicVolume(): number {
        return this._audioSource ? this._audioSource.volume : 1.0;
    }
}

export class Core {
    private static _instance: Core;
    public static get Instance(): Core {
        if (!this._instance) {
            this._instance = new Core();
        }
        return this._instance;
    }

    private constructor() {
        this.event = new EventCenter();
    }

    // ==================== 单例和基础配置 ====================
    private resourceVersion = "1.0.1";
    width = 400;
    height = 800;
    screenBounds: { minX: number; maxX: number; minY: number; maxY: number; };

    // ==================== 事件系统 ====================
    event: EventCenter;

    // ==================== 游戏状态 ====================
    currentView: EView | null;
    currentSubView: ESubView;
    currentMode: EGameMode;
    currentStage: number = 1;
    gameMode: EGameMode = EGameMode.Normal;
    gameResult: boolean = true;
    isNewPlayer: boolean = false;
    

    // ==================== 玩家相关 ====================
    currentPlayer: EPlayerType = EPlayerType.Tangmen;
    players: Map<EPlayerType, PlayerData> = new Map();
    selectedPlayers: Set<EPlayerType> = new Set();
    lastSelectedPlayers: EPlayerType[] = [];
    unlockedPlayers: EPlayerType[] = [];
    playerLockSet: Set<EPlayerType> = new Set([
        EPlayerType.Shaolin,
        EPlayerType.Tangmen,
        EPlayerType.Emei,
        EPlayerType.Wudang,
        EPlayerType.Gaibang
    ]);

    // ==================== 宠物相关 ====================
    currentPet: EPet = EPet.Pig;
    pets: Map<EPet, PetData> = new Map();
    selectedPets: Set<EPet> = new Set();
    unlockedPets: EPet[] = [];

    // ==================== 将军系统 ====================
    generalData: GeneralData = new GeneralData();

    // ==================== 资源系统 ====================
    gold: number = 0;
    renown = 0;
    junling = 0;
    exp = 0;
    people: number = 10000;
    food: number = 100;
    woodRock: number = 100;
    prisoner: number = 0;
    weather: EWeather = EWeather.Sunny;
    
    // [新加功能] 游戏天数
    day: number = 1;

    checkEquipmentRarity: Set<EItemQuality> = new Set();

    

    // ==================== 城墙系统 ====================
    wallLevel: number = 1;
    wallHp: number = 100;

    // ==================== 背包系统 ====================
    bag: BagManager = new BagManager();
    shopBag: BagManager = new BagManager();
    currentBagMode: string = '';
    currentItem: IItem;
    currentEquipmentType: EEquipmentType = EEquipmentType.Weapon;
    maxBag: number = 30;

    // ==================== 技能系统 ====================
    currentSkill: string;
    passiveSkills: any;

    // ==================== 时间管理系统 ====================
    lastRefreshShopTime: number = 0;
    lastJunlingRecoverTime: number = 0;
    lastJunlingDailyRecoverDate: string = "";
    lastResourceRecoverTime: number = 0;
    lastFoodRecoverTime: number = 0;
    lastWeatherUpdateDate: string = "";
    
    // [新加功能] 记录上一次每日逻辑执行的日期
    lastDailyResetDate: string = "";

    // ==================== 游戏进度系统 ====================
    unlockedModes: EGameMode[] = [EGameMode.Normal, EGameMode.Wood];
    unlockedStages: { [key: number]: boolean } = { 3: false, 7: false, 12: false, 19: false };
    unlockedSystem: Set<EGameSystem> = new Set();
    modeMaxStage: { [key in EGameMode]: number } = {
        [EGameMode.Normal]: 1,
        [EGameMode.Wood]: 1,
        [EGameMode.Endless]: 1,
        [EGameMode.Gold]: 1,
        [EGameMode.Pet]: 1,
        [EGameMode.Gem]: 1,
        [EGameMode.Boss]: 1
    };
    maxStage: number = 1;

    // ==================== 战场系统 ====================
    currentEnemy: EnemyManager = null!;
    nearestEnemy: Node;
    numOfDeadEnemies: number = 0;
    generalSkillMaxUses: number = 1;

    // 敌人配置
    endlessWaveConfigs;
    endlessEnemies: EEnemy[] = [];
    endlessBosses: EEnemy[] = [];
    normalEnemies: EEnemy[] = [];
    normalBosses: EEnemy[] = [];

    // ==================== 角色分配系统 ====================
    assignedPlayers: { [key in EMartialSystem]: EPlayerType } = {
        [EMartialSystem.Farming]: null,
        [EMartialSystem.Fortify]: null,
        [EMartialSystem.Search]: null,
        [EMartialSystem.Beacon]: null,
        [EMartialSystem.WoodRock]: null,
    };

    martialHeros: { [key in EMartialType]: EPlayerType | null } = {
        [EMartialType.Farming]: null,
        [EMartialType.Fortify]: null,
        [EMartialType.Beacon]: null
    };

    // ==================== 临时状态 ====================
    lastSpeedUp: number = 1;
    lastOpenArsenal: String;
    direction;
    currentBond: string;
    currentLink: string;
    data;
    loadedScene: Set<EScene> = new Set<EScene>();

    // ==================== 资源上限 ====================
    maxJunling = 120;
    maxFood = 10000;
    maxWoodRock = 10000;
    junlingGot = 0;
    // ==================== 玩家配置 ====================
    volume = 0.5;


    init(onFinished?: () => void) {
        console.log("Core初始化");
        this.calculateScreenBounds();
        this.createPlayers();
        this.createGeneral();
        this.createPet();



        // 初始化音频
        if (!director.getScene().getChildByName('AudioSourceNode')) {
            const audioNode = new Node('AudioSourceNode');
            director.getScene().addChild(audioNode);
            director.addPersistRootNode(audioNode);
            AudioManager.Instance.init(audioNode.addComponent(AudioSource));
        }
        // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据
        // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据
        //localStorage.removeItem(GAME_SAVING_KEY); // 仅在调试时使用，正式发布时请注释掉
        // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据
                // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据
                        // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据        // 清理数据
        // 加载数据   
        this.data = this.loadFromLocal();

        if (this.data) {
            console.log("老玩家数据加载");
            this.isNewPlayer = false;
            this.loadData(this.data);
        } else {
            console.log("新玩家初始化");
            this.isNewPlayer = true;
            this.junling = 120;
            this.lastResourceRecoverTime = this.getCurrentTime();
            // 新玩家初始化日期
            this.lastDailyResetDate = this.formatDate(new Date());
        }

        AudioManager.Instance.setMusicVolume(this.volume);

        // [核心修改] 统一处理所有每日刷新的逻辑（包括天数增加）
        this.checkDailyReset();

        // 统一资源恢复检查 (离线恢复)
        this.checkAndRecoverOfflineResources(this.getCurrentTime());
        this.loadActivePets();

        this.startResourceRecoveryTimer();
        onFinished?.();
    }

    calculateScreenBounds() {
        this.screenBounds = {
            minX: 0,
            maxX: 400,
            minY: 0,
            maxY: 800
        };
        console.log('屏幕边界（设计分辨率）：', this.screenBounds);
    }

    private startResourceRecoveryTimer() {
        setInterval(() => {
            this.resourceRecoverPer10Min();
            // 在定时器中也检查是否跨天了（防止玩家挂机过夜）
            this.checkDailyReset();
        }, RESOURCE_RECOVER_INTERVAL);
    }

    private loadData(data: any) {
        this.volume = data.volume !== undefined ? data.volume : 0.5;
        AudioManager.Instance.setMusicVolume(this.volume);

        this.lastResourceRecoverTime = data.lastResourceRecoverTime || 0;

        this.players.forEach((playerData, playerType) => {
            if (data[playerType]) {
                playerData.setPackage(data[playerType]);
            }
        });

        this.pets.forEach((petData, petType) => {
            if (data[petType]) {
                petData.setPackage(data[petType]);
            }
        });

        this.generalData.setPackage(data.generalData);

        // [新增] 加载解锁系统数据
        // 因为 JSON 不支持 Set，存档里存的是 Array，这里要转回 Set
        if (data.unlockedSystem && Array.isArray(data.unlockedSystem)) {
            this.unlockedSystem = new Set(data.unlockedSystem);
        } else {
            this.unlockedSystem = new Set();
        }

        // Global Data
        this.day = data.day || 1; // 加载天数
        this.gold = data.gold || 0;
        this.renown = data.renown || 0;
        this.unlockedPlayers = data.unlockedPlayers || [];
        this.unlockedPets = data.unlockedPets || [];
        this.unlockedStages = data.unlockedStages || {};
        this.junling = data.junling || 0;
        this.exp = data.exp || 0;
        this.selectedPlayers = new Set(data.selectedPlayers);
        this.lastSelectedPlayers = data.lastSelectedPlayers || [];
        this.bag.setPackage(data.bag?.items || []);
        this.shopBag.setPackage(data.shopBag?.items || []);
        this.playerLockSet = new Set(data.playerLockSet || []);
        this.prisoner = data.prisoner || 0;
        this.modeMaxStage = data.modeMaxStage
        this.wallLevel = data.wallLevel || 1;
        this.assignedPlayers = data.assignedPlayers || {}
        this.people = data.people || 50000;
        this.wallHp = data.wallHp
        this.food = data.food
        this.woodRock = data.woodRock
        this.generalSkillMaxUses = data.generalSkillMaxUses || 1
        this.lastSpeedUp = data.lastSpeedUp
        this.lastRefreshShopTime = data.lastRefreshShopTime


        this.lastJunlingDailyRecoverDate = data.lastJunlingDailyRecoverDate;
        this.lastJunlingRecoverTime = data.lastJunlingRecoverTime;

        // 【修改点 2】关键！必须把 Array 转回 Set
        // 如果直接写 this.checkEquipmentRarity = data.checkEquipmentRarity
        // 它就会变成数组，调用 .has() 就会报错！
        if (data.checkEquipmentRarity && Array.isArray(data.checkEquipmentRarity)) {
            this.checkEquipmentRarity = new Set(data.checkEquipmentRarity);
            console.log("存档里存有筛选 checkEquipmentRarity，已恢复this.checkEquipmentRarity",this.checkEquipmentRarity);
        } else {
            // 如果存档里没有，或者格式不对，初始化为空 Set
            this.checkEquipmentRarity = new Set();
        }
        
        // 天气与日期
        this.weather = data.weather || EWeather.Sunny;
        this.lastWeatherUpdateDate = data.lastWeatherUpdateDate;
        this.lastDailyResetDate = data.lastDailyResetDate || this.lastWeatherUpdateDate || ""; // 兼容旧档
    }

    private loadFromLocal(): any {
        try {
            const data = localStorage.getItem(GAME_SAVING_KEY);
            return data ? JSON.parse(data) : null;
        } catch (e) {
            console.error('Core加载存档失败:', e);
            return null;
        }
    }

    private savePlayerData(data: any) {
        this.players.forEach((playerData, playerType) => {
            data[playerType] = playerData.getPackage();
        });
    }

    private saveGeneralData(data: any) {
        data.generalData = this.generalData.getPackage();
    }

    private savePetData(data: any) {
        this.pets.forEach((petData, petType) => {
            data[petType] = petData.getPackage();
        });
    }

    private saveGlobalData(data: any) {
        // [新增] 保存解锁系统数据
        // Set 转 Array 以便 JSON 序列化
        data.unlockedSystem = Array.from(this.unlockedSystem);

        data.day = this.day; // 保存天数
        data.gold = this.gold;
        data.exp = this.exp;
        data.renown = this.renown;
        data.junling = this.junling;
        data.people = this.people;
        data.unlockedPlayers = this.unlockedPlayers;
        data.unlockedPets = this.unlockedPets;
        data.unlockedStages = this.unlockedStages;
        data.selectedPlayers = Array.from(this.selectedPlayers);
        data.lastSelectedPlayers = this.lastSelectedPlayers;
        data.bag = { items: this.bag.getPackage() };
        data.shopBag = { items: this.shopBag.getPackage() };
        data.playerLockSet = Array.from(this.playerLockSet);
        data.prisoner = this.prisoner;
        data.modeMaxStage = this.modeMaxStage;
        data.wallLevel = this.wallLevel;
        data.wallHp = this.wallHp;
        data.assignedPlayers = this.assignedPlayers;
        data.food = this.food;
        data.woodRock = this.woodRock;
        data.generalSkillMaxUses = this.generalSkillMaxUses;
        data.lastSpeedUp = this.lastSpeedUp;
        data.weather = this.weather;
// 【修改点 3】Set 转 Array 再保存
        // 如果不转，JSON.stringify(set) 会变成 {}，导致数据丢失
        data.checkEquipmentRarity = Array.from(this.checkEquipmentRarity);

        
        data.volume = AudioManager.Instance.getMusicVolume();
    }

    private saveTimeData(data: any) {
        data.lastJunlingDailyRecoverDate = this.lastJunlingDailyRecoverDate;
        data.lastJunlingRecoverTime = this.lastJunlingRecoverTime;
        data.lastRefreshShopTime = this.lastRefreshShopTime;
        data.lastResourceRecoverTime = this.lastResourceRecoverTime;
        data.lastWeatherUpdateDate = this.lastWeatherUpdateDate;
        data.lastDailyResetDate = this.lastDailyResetDate; // 保存最后跨天日期
    }

    save(savePlayerData: boolean = true) {
        const data: any = {};
        if (savePlayerData) {
            this.savePlayerData(data);
        }
        this.saveGeneralData(data);
        this.savePetData(data);
        this.saveGlobalData(data);
        this.saveTimeData(data);

        try {
            localStorage.setItem(GAME_SAVING_KEY, JSON.stringify(data));
        } catch (e) {
            console.warn('保存失败:', e);
        }
    }

    createPlayers() {
        Util.getObjectValues(EPlayerType).forEach(playerType => {
            if (!this.players.has(playerType)) {
                const playerData = new PlayerData(playerType);
                if (this.isNewPlayer) {
                    playerData.calculateAttributes();
                }
                this.players.set(playerType, playerData);
            }
        });
    }

    createGeneral() {
        this.generalData = new GeneralData();
    }

    createPet() {
        Util.getObjectValues(EPet).forEach(petType => {
            if (!this.pets.has(petType)) {
                this.pets.set(petType, new PetData(petType));
            }
        });
    }

    async startBackgroundPreload(scene: EScene) {
        const res = sceneRes[scene];
        if (!res || res.length === 0) return;
        await this.startBackgroundPreload_assetList(res);
    }

    async startBackgroundPreload_assetList(assetList: string[]) {
        if (!assetList || assetList.length === 0) return;
        const atlasPaths = assetList.filter(p => p.endsWith("AutoAtlas"));
        const prefabPaths = assetList.filter(p => p.includes("prefab"));
        const otherPaths = assetList.filter(p => !p.endsWith("AutoAtlas") && !p.includes("prefab"));

        try {
            if (atlasPaths.length > 0) await ResourceManager.Instance.loadResArray(atlasPaths, SpriteAtlas);
            if (prefabPaths.length > 0) await ResourceManager.Instance.loadResArray(prefabPaths, Prefab);
            if (otherPaths.length > 0) await ResourceManager.Instance.loadResArray(otherPaths, SpriteFrame);
        } catch (error) {
            console.warn(`[Core] 加载失败：`, error);
        }
    }

    

    /**
     * [核心逻辑] 判断某系统是否已解锁
     * Main.ts 的 UI 按钮会调用这个方法
     */
    isSystemUnlocked(system: EGameSystem): boolean {
        // 1. 如果存档里记录已解锁，直接返回 true
        if (this.unlockedSystem.has(system)) return true;

        // 2. 获取配置
        const config = SystemUnlockMap.get(system);
        
        // 3. 如果配置表里没有这个系统（比如背包 Bag），默认视为已解锁，不设限
        if (!config) return true;

        // 4. 兜底检查：如果当前普通模式的最大关隘已经达到了要求，也视为解锁
        // (防止有时候弹窗没触发但关隘已经打过去了)
        if (this.modeMaxStage[EGameMode.Normal] >= config.unlockStage) {
            this.unlockedSystem.add(system); // 补录数据
            return true;
        }

        return false;
    }

    /**
     * [核心逻辑] 检测是否有新系统解锁
     * 建议调用时机：战斗胜利结算界面 (WinView) 或 回到主界面时
     */
checkNewSystemUnlock() {
        // 获取当前关隘进度
        const currentStage = this.modeMaxStage[EGameMode.Normal];
        
        // 标记是否有新解锁（用于决定是否保存和刷新UI）
        let hasNewUnlock = false;

        for (const config of SystemUnlockConfig) {
            // 判断条件：当前关隘 >= 解锁关隘  并且  数据里还没有记录解锁
            if (currentStage >= config.unlockStage && !this.unlockedSystem.has(config.id)) {
                
                // 1. 数据层标记解锁
                this.unlockedSystem.add(config.id);
                hasNewUnlock = true;
                
                // 2. 弹出解锁提示框
                this.showUnlockPop(
                    "功能解锁", 
                    // 提示文案：例如 "恭喜通过第2关，已开启 [军需商店]"
                    `恭喜通过第 ${config.unlockStage - 1} 关\n已开启 [${config.name}]`, 
                    config.icon, // 使用配置表里的图标路径
                    () => {
                        // 弹窗关闭后的回调
                        console.log(`玩家已查看解锁提示: ${config.name}`);
                        
                        // [可选] 如果你在主界面，可以通知主界面刷新按钮状态
                        // this.event.emit(EEvent.RefreshUnlockUI); 
                    }
                );
                
                console.log(`[Core] 系统自动解锁: ${config.name}`);
            }
        }

        // 3. 如果有新解锁，保存存档
        if (hasNewUnlock) {
            this.save();
        }
    }

    

    // ==================== [核心逻辑] 每日重置系统 ====================
    /**
     * [新加功能] 统一的每日重置检测
     * 包含：天数增加、天气更新、每日军令恢复
     */
    private checkDailyReset() {
        const today = this.formatDate(new Date());

        // 如果没有记录，初始化为今天
        if (!this.lastDailyResetDate) {
            this.lastDailyResetDate = today;
            this.lastWeatherUpdateDate = today;
            this.lastJunlingDailyRecoverDate = today;
            return;
        }

        // 检查是否跨天
        if (this.lastDailyResetDate !== today) {
            console.log(`[Core] 检测到新的一天: ${today} (旧日期: ${this.lastDailyResetDate})`);
            
            // 1. 增加游戏天数
            this.day++;
            console.log(`游戏第 ${this.day} 天`);

            // 2. 更新天气
            this.updateDailyWeather();
            this.lastWeatherUpdateDate = today;

            // 3. 恢复每日军令
            this.junling = Math.min(this.junling + JUNLING_RECOVER_AMOUNT_DAILY, 1000);
            this.junlingGot += JUNLING_RECOVER_AMOUNT_DAILY;
            this.lastJunlingDailyRecoverDate = today;
            console.log(`每日军令恢复 +${JUNLING_RECOVER_AMOUNT_DAILY}`);

            // 4. 更新记录日期并保存
            this.lastDailyResetDate = today;
            this.save();
        }
    }

    // 更新每日天气（随机选择）
    private updateDailyWeather() {
        const allWeathers = [EWeather.Sunny, EWeather.Rainy, EWeather.Foggy, EWeather.Cloudy, EWeather.Windy];
        const randomIndex = Math.floor(Math.random() * allWeathers.length);
        this.weather = allWeathers[randomIndex];
        console.log(`天气更新为: ${EWeather[this.weather]}`);
    }

    // ==================== Tips 和 UI 辅助 ====================

    showTips(msg: string, position: { x: number, y: number } = { x: 0, y: 0 }, duration: number = 3) {
        try {
            const node = ObjectPoolManager.Instance.get(ETips.Tips);
            const tipsManager = node.getComponent(TipsManager);
            if (!tipsManager) return;
            
            let parent = find("Canvas");
            tipsManager.init(msg);
            parent.addChild(node);
            node.setPosition(position.x, position.y, 0);

            const opacity = node.getComponent(UIOpacity);
            if(opacity) opacity.opacity = 255; // 确保显示

            tween(opacity)
                .to(duration, { opacity: 0 })
                // [优化4] 使用对象池时，必须用 ret 回收，不能 destroy
                // 如果 ETips.Tips 真的是受对象池管理的，请确保 TipsManager 清理了状态
                .call(() => { 
                    if (node && node.isValid) {
                        // 如果确定要销毁而不是回收，保持 node.destroy()
                        // 如果要回收: ObjectPoolManager.Instance.ret(node);
                        node.destroy(); 
                    }
                }) 
                .start();
        } catch (error) {
            console.error("显示Tips失败:", error);
        }
    }

    /**
     * 通用解锁弹窗
     * @param title 标题 (如: "功能解锁")
     * @param content 内容 (如: "已开启 宠物 系统")
     * @param iconPath 图标路径 (可选，如: "pic/ui/icon_pet")
     * @param callback 关闭后的回调 (可选)
     */
    showUnlockPop(title: string, content: string, iconPath: string = "", callback?: Function) {
        // 1. 加载 Prefab
        ResourceManager.Instance.loadRes(PrefabPath.UnlockTip, Prefab).then((prefab) => {
            if (!prefab) return;

            const node = instantiate(prefab);
            const parent = Context.ndTips
            parent.addChild(node);
            node.setPosition(0, 0, 0); // 居中

            // 2. 获取节点引用 (假设节点结构如上所述)
            const ndBoard = node.getChildByName("Board");
            const lbTitle = ndBoard.getChildByName("Title")?.getComponent(Label);
            const lbContent = ndBoard.getChildByName("Content")?.getComponent(Label);
            const spIcon = ndBoard.getChildByName("Icon")?.getComponent(Sprite);

            // 3. 赋值
            if (lbTitle) lbTitle.string = title;
            if (lbContent) lbContent.string = content;

            // 4. 加载图标 (如果有)
            if (iconPath && spIcon) {
                ResourceManager.Instance.loadRes(iconPath, SpriteFrame).then((sf) => {
                    if (spIcon && spIcon.isValid && sf) {
                        spIcon.spriteFrame = sf;
                    }
                });
            }

            // 5. 入场动画 (Board 从 0 缩放到 1，带弹性)
            ndBoard.scale = v3(0, 0, 1);
            tween(ndBoard)
                .to(0.5, { scale: v3(1, 1, 1) }, { easing: 'backOut' })
                .start();

            // 6. 播放音效 (可选)
            AudioManager.Instance.playSound("unlocktip"); 

            // 7. 点击关闭逻辑 (点击任意地方关闭，或者你可以指定一个确定按钮)
            node.on(Node.EventType.TOUCH_END, () => {
                // 出场动画
                tween(ndBoard)
                    .to(0.3, { scale: v3(0, 0, 1) }, { easing: 'backIn' })
                    .call(() => {
                        node.destroy();
                        if (callback) callback();
                    })
                    .start();
            }, this);
        }).catch(err => {
            console.error("加载解锁弹窗失败:", err);
        });
    }
    
    // Core.ts

    /**
     * 显示通用确认框 (无需额外脚本版)
     * @param title 标题
     * @param content 内容
     * @param onConfirm 点击确定的回调
     * @param onCancel 点击取消的回调（可选）
     */
    showConfirm(title: string, content: string, onConfirm: Function, onCancel?: Function) {
        // 1. 加载 Prefab
        ResourceManager.Instance.loadRes(PrefabPath.ConfirmPop, Prefab).then((prefab) => {
            if (!prefab) return;

            // 2. 实例化并挂载
            const node = instantiate(prefab);
            // 挂载到 ndTips 下面，确保在最上层
            const parent = Context.ndTips || find("Canvas"); 
            parent.addChild(node);
            node.setPosition(0, 0, 0);

            // 3. 获取子节点 (请确保 Prefab 里的名字和这里一致！)
            const ndBoard = node.getChildByName("Board");
            // 容错处理：万一没找到 Board，就直接用根节点
            const targetBoard = ndBoard || node; 

            const lbTitle = targetBoard.getChildByName("Title")?.getComponent(Label);
            const lbContent = targetBoard.getChildByName("Content")?.getComponent(Label);
            const btnConfirm = targetBoard.getChildByName("BtnConfirm");
            const btnCancel = targetBoard.getChildByName("BtnCancel");

            // 4. 设置文本
            if (lbTitle) lbTitle.string = title;
            if (lbContent) lbContent.string = content;

            // 5. 定义关闭动画函数
            const close = () => {
                tween(targetBoard)
                    .to(0.2, { scale: v3(0, 0, 1) }, { easing: 'backIn' })
                    .call(() => {
                        if (node && node.isValid) node.destroy();
                    })
                    .start();
            };

            // 6. 绑定点击事件 (核心部分)
            
            // --- 确认按钮 ---
            if (btnConfirm) {
                btnConfirm.on(Node.EventType.TOUCH_END, () => {
                    AudioManager.Instance.playSound("click"); // 播放通用点击音效
                    if (onConfirm) onConfirm();
                    close();
                }, this);
            } else {
                console.warn("ConfirmPop: 没找到 BtnConfirm 节点");
            }

            // --- 取消按钮 ---
            if (btnCancel) {
                btnCancel.on(Node.EventType.TOUCH_END, () => {
                    AudioManager.Instance.playSound("click");
                    if (onCancel) onCancel();
                    close();
                }, this);
            }

            // --- 遮罩背景点击 (可选：点击背景关闭) ---
            // node.on(Node.EventType.TOUCH_END, () => {
            //     if (onCancel) onCancel();
            //     close();
            // });

            // 7. 入场动画
            targetBoard.scale = v3(0, 0, 1);
            tween(targetBoard)
                .to(0.3, { scale: v3(1, 1, 1) }, { easing: 'backOut' })
                .start();

        }).catch(err => {
            console.error("加载确认框失败:", err);
        });
    }

    showLabel(msg: string, color: Color = Color.YELLOW, fontsize: number = 20, lineHeight: number = 20, parent: Node = Context.ndDefault, position = { x: 0, y: 0 }, duration: number = 3) {
        resources.load(PrefabPath.Label, Prefab, (err, prefab) => {
            if (err) return;
            const node = instantiate(prefab);
            node.name = "Label";
            node.setPosition(position.x, position.y, 0);
            parent.addChild(node);
            
            const label = node.getComponent(Label);
            if (label) {
                label.string = msg;
                label.fontSize = fontsize;
                label.lineHeight = lineHeight;
                label.color = color;
            }

            const opacity = node.getComponent(UIOpacity);
            if(opacity) opacity.opacity = 255;

            tween(opacity)
                .to(duration, { opacity: 0 })
                .call(() => { if (node && node.isValid) node.destroy(); })
                .start();
        });
    }

    showItemGot(item, parent: Node = Context.ndTips, position = { x: 0, y: 0 }, duration: number = 3) {
        try {
            const node = ObjectPoolManager.Instance.get(ETips.Board_ItemGot);
            const labelComponent = node.getChildByName('Board').getChildByName("ItemGot").getComponent(Label);
            if (!labelComponent) return;

            labelComponent.string = `获得${item.name} ${item.count}`;
            labelComponent.fontSize = 15;
            labelComponent.lineHeight = 15;
            labelComponent.color = Color.YELLOW;

            parent.addChild(node);
            node.setPosition(position.x, position.y, 0);

            tween(node)
                .to(0.5, { scale: v3(1, 1, 1) }, { easing: 'backOut' })
                .start();
            // 注意：这里没有写回收逻辑，通常需要在外部或点击后回收
        } catch (error) {
            console.error("弹窗展示获得的物品失败:", error);
        }
    }

    showTweenLabel(msg: string, parent: Node = Context.ndTips, color: Color = Color.WHITE, fontsize: number = 20, lineHeight: number = 20, position = { x: 0, y: 0 }, duration: number = 3) {
        const node = new Node();
        node.name = "TweenLabel";
        const label = node.addComponent(Label);
        label.string = msg;
        label.fontSize = fontsize;
        label.lineHeight = lineHeight;
        label.color = color;
        parent.addChild(node);
        node.setPosition(position.x, position.y, 0);
        
        tween(node)
            .to(0.5, { scale: v3(1, 1, 1) }, { easing: 'backOut' })
            .call(() => { node.destroy(); })
            .start();
    }

    loadActivePets() {
        Core.Instance.pets.forEach((petData, petType) => {
            if (petData.assigned) {
                this.selectedPets.add(petType);
            }
        });
        console.log("loadActivePets:", this.selectedPets);
    }

    private checkAndUpdateWeather() {
        // 此方法已整合进 checkDailyReset，保留空壳或直接删除调用
    }

    getWeatherName(): string {
        return EWeather[this.weather];
    }

    // ==================== 资源恢复逻辑 ====================

    private checkAndRecoverOfflineResources(now: number) {
        if (!this.lastResourceRecoverTime) {
            this.lastResourceRecoverTime = now;
            return;
        }

        const offlineMinutes = Math.floor((now - this.lastResourceRecoverTime) / (1000 * 60));

        if (offlineMinutes >= 10) {
            const recoverIntervals = Math.floor(offlineMinutes / 10);

            if (this.junling < this.maxJunling) {
                const junlingRecoverAmount = recoverIntervals * JUNLING_RECOVER_AMOUNT;
                this.junling = Math.min(this.junling + junlingRecoverAmount, this.maxJunling);
            }

            if (this.food < this.maxFood) {
                const foodRecoverAmount = recoverIntervals * this.calculateFoodRecoverAmount();
                this.food = Math.min(this.food + foodRecoverAmount, this.maxFood);
            }

            if (this.woodRock < this.maxWoodRock) {
                const woodRockRecoverAmount = recoverIntervals * this.calculateWoodRockRecoverAmount();
                this.woodRock = Math.min(this.woodRock + woodRockRecoverAmount, this.maxWoodRock);
            }
            // 这里也顺带检查一下军令
            this.checkAndRecoverOfflineJunling();
            
            console.log(`离线资源恢复完成: ${offlineMinutes}分钟`);
        }
    }

    checkAndRecoverOfflineJunling() {
        const now = Date.now();
        if (!this.lastJunlingRecoverTime) {
            this.lastJunlingRecoverTime = now;
            return;
        }
        const offlineMinutes = Math.floor((now - this.lastJunlingRecoverTime) / (1000 * 60));
        
        if (offlineMinutes >= 10) {
            const recoverAmount = Math.floor(offlineMinutes / 10); // 假设10分钟恢复1点，按需配置
            // 注意：这里逻辑和 resourceRecoverPer10Min 有点重复，但 context 不同
            // 这里是专门针对 lastJunlingRecoverTime 的
            this.lastJunlingRecoverTime = now;
        }
    }

    private calculateFoodRecoverAmount(): number {
        const peopleUnits = Math.floor(this.people / 10000);
        return peopleUnits * FOOD_RECOVER_RATE;
    }

    private calculateWoodRockRecoverAmount(): number {
        const peopleUnits = Math.floor(this.people / 10000);
        return peopleUnits * WOODROCK_RECOVER_RATE;
    }

resourceRecoverPer10Min() {
        const now = Date.now();
        if (!this.lastResourceRecoverTime) {
            this.lastResourceRecoverTime = now;
            return;
        }

        if (this.junling < this.maxJunling) {
            this.junling += JUNLING_RECOVER_AMOUNT;
        }

        const foodRecoverAmount = this.calculateFoodRecoverAmount();
        this.food += foodRecoverAmount;

        const woodRockRecoverAmount = this.calculateWoodRockRecoverAmount();
        this.woodRock += woodRockRecoverAmount;

        this.lastResourceRecoverTime = now;
        console.log(`在线资源恢复执行`);
        this.save();
        
        // 【关键】必须发送事件，通知 Main 刷新界面
        this.event.emit(EEvent.ResourceChanged); 
    }

    private getCurrentTime(): number {
        return Date.now();
    }

    private formatDate(date: Date): string {
        const year = date.getFullYear();
        const month = ('0' + (date.getMonth() + 1)).slice(-2);
        const day = ('0' + date.getDate()).slice(-2);
        return year + '-' + month + '-' + day;
    }

    // ==================== 物品与掉落 ====================

    calculateDropCountByStage(stage: number): number {
        return Math.min(1 + Math.floor(stage / 5), 10);
    }

    calculateDropQuality(stage: number): number {
        const qualityProbabilities = [
            { min: 1, max: 5, probabilities: [80, 20, 0, 0, 0] },
            { min: 6, max: 10, probabilities: [70, 30, 0, 0, 0] },
            { min: 11, max: 15, probabilities: [50, 40, 10, 0, 0] },
            { min: 16, max: 20, probabilities: [30, 40, 20, 10, 0] },
            { min: 21, max: 25, probabilities: [10, 20, 30, 30, 10] },
            { min: 26, max: 30, probabilities: [5, 15, 25, 35, 19, 1] }
        ];

        const range = qualityProbabilities.find(r => stage >= r.min && stage <= r.max);
        if (!range) return 1;

        const rand = Math.random() * 100;
        let cumulative = 0;
        for (let i = 0; i < range.probabilities.length; i++) {
            cumulative += range.probabilities[i];
            if (rand < cumulative) {
                return i + 1;
            }
        }
        return 1;
    }

    generateRandomEquipment(quality: number): Equipment {
        const possibleTemplates = Object.keys(EquipmentConfig).map(
            key => (EquipmentConfig as any)[key]
        ) as IEquipment[];
        const filteredTemplates = possibleTemplates.filter(t => t.quality <= quality);
        if (filteredTemplates.length === 0) return null;
        const randomIndex = Math.floor(Math.random() * filteredTemplates.length);
        return new Equipment(filteredTemplates[randomIndex]);
    }

    generateRandomGem(quality: number): IItem {
        const gemTemplates = Object.keys(GemConfig).map(
            key => (GemConfig as any)[key]
        ) as IGem[];
        const filteredTemplates = gemTemplates.filter(t => t.quality <= quality);
        if (filteredTemplates.length === 0) return null;
        const randomIndex = Math.floor(Math.random() * filteredTemplates.length);
        return new Gem(filteredTemplates[randomIndex]);
    }

    generateLootItems(stage: number, count: number): IItem[] {
        const lootList: IItem[] = [];
        for (let i = 0; i < count; i++) {
            const quality = this.calculateDropQuality(stage);
            if (Math.random() > 0.2) {
                const equipment = this.generateRandomEquipment(quality);
                if (equipment) lootList.push(equipment);
            } else {
                const gem = this.generateRandomGem(quality);
                if (gem) lootList.push(gem);
            }
        }
        return lootList;
    }

    generateItem(type: EItemType, quality: number): IItem | IEquipment | null {
        if (type == EItemType.Equipment) {
            return this.generateRandomEquipment(quality);
        } else if (type == EItemType.Gem) {
            return this.generateRandomGem(quality);
        }
        return null;
    }

    // ==================== 战场辅助 ====================

    getEndlessEnemys() {
        this.endlessEnemies = this.getRandomEnemies(4);
        this.endlessBosses = [this.getRandomBoss(), this.getRandomBoss()];
    }

    getNormalEnemys() {
        let stage = this.modeMaxStage[EGameMode.Normal];
        const stageConfig = StageConfig[EGameMode.Normal].stage[stage];
        if (!stageConfig) {
            this.normalEnemies = [];
            this.normalBosses = [];
            return;
        }
        const enemySet = new Set<EEnemy>();
        stageConfig.waves.forEach(wave => {
            Object.keys(wave.enemies).forEach(enemyKey => {
                enemySet.add(enemyKey as EEnemy);
            });
        });
        this.normalEnemies = Array.from(enemySet);
        this.normalBosses = stageConfig.bosses || [];
    }

    private getRandomEnemies(count: number): EEnemy[] {
        const shuffled = [...enemyList].sort(() => 0.5 - Math.random());
        return shuffled.slice(0, count);
    }

    private getRandomBoss(): EEnemy {
        const randomIndex = Math.floor(Math.random() * bossList.length);
        return bossList[randomIndex];
    }

    getFileListFromEnemyConfig(): string[] {
        return this.getAllEnemyFileList();
    }

    getAllEnemyFileList(): string[] {
        return [
            "pic/enemy/AutoAtlas",
            "pic/skill/AutoAtlas",
            "pic/equipment/AutoAtlas",
            "pic/other/AutoAtlas",
            "pic/player/AutoAtlas",
            "pic/pet/AutoAtlas"
        ];
    }

    getProfessionDesc(profession: EPlayerType) {
        switch (profession) {
            case EPlayerType.Shaolin: return "少林";
            case EPlayerType.Tangmen: return "唐门";
            case EPlayerType.Wudang: return "武当";
            case EPlayerType.Emei: return "峨眉";
            case EPlayerType.Gaibang: return "丐帮";
            default: return "通用";
        }
    }

    // ==================== 商店逻辑 ====================

    shouldRefreshShop(): boolean {
        const now = this.getCurrentTime();
        return now - this.lastRefreshShopTime >= SHOP_REFRESH_INTERVAL;
    }

    refreshShop(): boolean {
        if (this.shouldRefreshShop()) {
            this.lastRefreshShopTime = this.getCurrentTime();
            this.save();
            return true;
        }
        return false;
    }

    getShopRefreshRemainingTime(): number {
        const currentTime = Math.floor(Date.now() / 1000);
        const lastRefreshTime = Math.floor(this.lastRefreshShopTime / 1000);
        const elapsed = currentTime - lastRefreshTime;
        const remaining = SHOP_REFRESH_INTERVAL / 1000 - elapsed;
        return Math.max(0, remaining);
    }
}